package com.tyk.bookstore.front.member.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.tyk.bookstore.front.common.core.model.enume.PrincipalType;
import com.tyk.bookstore.front.common.core.model.exception.CustomException;
import com.tyk.bookstore.front.common.core.model.result.CommonResultEnum;
import com.tyk.bookstore.front.common.core.util.*;
import com.tyk.bookstore.front.common.other.code.behavior.manager.BehaviorCodeManager;
import com.tyk.bookstore.front.common.other.code.behavior.model.enume.SceneType;
import com.tyk.bookstore.front.common.other.code.msg.manager.MsgCodeManager;
import com.tyk.bookstore.front.common.other.code.msg.model.enume.MsgCodeType;
import com.tyk.bookstore.front.common.other.oss.manager.OssManager;
import com.tyk.bookstore.front.common.security.util.PasswordUtil;
import com.tyk.bookstore.front.member.manager.MemberRedisManager;
import com.tyk.bookstore.front.member.model.constant.OssConstant;
import com.tyk.bookstore.front.member.model.entity.MemberDetailDo;
import com.tyk.bookstore.front.member.model.entity.MemberDo;
import com.tyk.bookstore.front.member.model.enume.CenterUpdateType;
import com.tyk.bookstore.front.member.model.query.*;
import com.tyk.bookstore.front.member.model.result.MemberResultEnum;
import com.tyk.bookstore.front.member.model.vo.MemberVo;
import com.tyk.bookstore.front.member.repository.MemberDetailRepository;
import com.tyk.bookstore.front.member.repository.MemberRepository;
import com.tyk.bookstore.front.member.service.MemberCenterService;
import com.tyk.bookstore.front.member.util.MapStructMember;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class MemberCenterServiceImpl implements MemberCenterService {
    private final MemberRepository memberRepository;
    private final MemberDetailRepository memberDetailRepository;
    private final OssManager ossManager;
    private final MemberRedisManager memberRedisManager;
    private final MapStructMember mapStructMember;
    private final BehaviorCodeManager behaviorCodeManager;
    private final MsgCodeManager msgCodeManager;


    @Override
    public String sendMsgCodeWithBehaviorCheck(MsgCodeSendWithBehaviorCheckLoggedQuery query, SceneType scene) {

        long memberId = StpUtil.getLoginIdAsLong();

        // 校验行为验证码
        boolean check = behaviorCodeManager.check(scene, query.getVerifyParam());
        AssertUtil.isTrue(check, CommonResultEnum.CODE_BEHAVIOR_ERROR);

        // 查询手机号
        String phone = memberRepository.<String>selectSin1Eq(
                MemberDo::getPhone,
                MemberDo::getId, memberId,
                MemberDo.class
        ).getV1();

        // 发送验证码
        String code = msgCodeManager.sendCodeAsync(MsgCodeType.PHONE_ALI, phone);
//        String code = "123456";

        // 生成并缓存消息验证码凭证
        String verifyCode = ComUtil.simUuid();
        memberRedisManager.cacheMsgCode(scene, verifyCode, phone, query.getType(), code);

        // 返回消息验证码凭证
        return verifyCode;
    }

    @Override
    public String sendMsgCodeNotRegistered(MsgCodeSendNotRegisteredQuery query, SceneType scene) {

        // 获取验证码类型
        PrincipalType type = ParamUtil.of(query.getPrincipal());
        AssertUtil.notNull(type, CommonResultEnum.PARAM_ERROR);
        MsgCodeType ms = switch (type) {
            case PHONE -> {
                AssertUtil.isTrue(
                        memberRepository.exist1Eq(
                                MemberDo::getPhone, query.getPrincipal(), MemberDo.class
                        ),
                        MemberResultEnum.PHONE_HAS_REGISTERED
                );
                yield MsgCodeType.PHONE_ALI;
            }
            case EMAIL -> {
                AssertUtil.isTrue(
                        memberRepository.exist1Eq(
                                MemberDo::getEmail, query.getPrincipal(), MemberDo.class
                        ),
                        MemberResultEnum.EMAIL_HAS_BOUND
                );
                yield MsgCodeType.EMAIL_QQ;
            }
            default -> throw new CustomException(CommonResultEnum.PARAM_ERROR);
        };

        // 发送验证码
        String code = msgCodeManager.sendCodeAsync(ms, query.getPrincipal());
//        String code = "123456";

        // 生成并缓存消息验证码凭证
        String codeCertificate = ComUtil.simUuid();
        memberRedisManager.cacheMsgCode(scene, codeCertificate, query.getPrincipal(), query.getType(), code);

        // 返回消息验证码凭证
        return codeCertificate;
    }


    @Override
    public String checkMsgCode(MsgCodeCheckLoggedQuery query, SceneType scene) {

        // 获取手机号
        long memberId = StpUtil.getLoginIdAsLong();
        String phone = memberRepository.<String>selectSin1Eq(
                MemberDo::getPhone,
                MemberDo::getId, memberId,
                MemberDo.class
        ).getV1();

        // 校验消息验证码
        String code = memberRedisManager.getMsgCode(scene, query.getCodeCertificate(), phone, query.getType());
        AssertUtil.isTrue(
                ObjUtil.equal(code, query.getCertificate()),
                CommonResultEnum.CODE_MSG_PHONE_ERROR_OR_EXPIRED
        );

        // 生成消息验证码校验通过凭证
        String certificate = ComUtil.simUuid();
        memberRedisManager.cacheMsgPassCertificate(scene, certificate, phone, query.getType());

        // 返回验证码校验通过凭证
        return certificate;

    }


    @Override
    public void updatePhone(PhoneUpdateQuery query) {

        // 校验消息验证码通过凭证
        assertCertificateRight(query.getCodePassedCertificate(), CenterUpdateType.PHONE);

        // 校验新手机号验证码
        String code = memberRedisManager.getMsgCode(SceneType.CENTER, query.getNewCodeCertificate(), query.getNewPhone(), CenterUpdateType.PHONE);
        AssertUtil.equal(code, query.getNewCertificate(), CommonResultEnum.CODE_MSG_PHONE_ERROR_OR_EXPIRED);

        // 校验新手机号是否注册
        AssertUtil.isFalse(
                memberRepository.exist1Eq(
                        MemberDo::getPhone, query.getNewPhone(), MemberDo.class
                ),
                MemberResultEnum.PHONE_HAS_REGISTERED
        );

        // 更新手机号
        memberRepository.update1Set1Eq(
                MemberDo::getPhone, query.getNewPhone(),
                MemberDo::getId, StpUtil.getLoginIdAsLong(),
                MemberDo.class
        );
    }

    @Override
    public void updateEmail(EmailUpdateQuery query) {

        // 校验消息验证码通过凭证
        assertCertificateRight(query.getCodePassedCertificate(), CenterUpdateType.EMAIL);

        // 校验新邮箱验证码
        String code = memberRedisManager.getMsgCode(SceneType.CENTER, query.getNewCodeCertificate(), query.getNewEmail(), CenterUpdateType.EMAIL);
        AssertUtil.equal(code, query.getNewCertificate(), CommonResultEnum.CODE_MSG_PHONE_ERROR_OR_EXPIRED);

        // 校验新邮箱
        AssertUtil.isFalse(
                memberRepository.exist1Eq(
                        MemberDo::getEmail, query.getNewEmail(), MemberDo.class
                ),
                MemberResultEnum.EMAIL_HAS_BOUND
        );

        // 更新邮箱
        memberRepository.update1Set1Eq(
                MemberDo::getEmail, query.getNewEmail(),
                MemberDo::getId, StpUtil.getLoginIdAsLong(),
                MemberDo.class
        );

    }

    @Override
    public void updateLoginPassword(LoginPasswordUpdateQuery query) {
        // 校验消息验证码通过凭证
        assertCertificateRight(query.getCodePassedCertificate(), CenterUpdateType.PASSWORD_LOGIN);
        // 更新登录密码
        memberRepository.update1Set1Eq(
                MemberDo::getPassword, PasswordUtil.encrypt(query.getPassword()),
                MemberDo::getId, StpUtil.getLoginIdAsLong(),
                MemberDo.class
        );
    }

    @Override
    public void updatePayPassword(PayPasswordUpdateQuery query) {
        // 校验消息验证码通过凭证
        assertCertificateRight(query.getCodePassedCertificate(), CenterUpdateType.PASSWORD_PAY);
        // 更新支付密码
        memberRepository.update1Set1Eq(
                MemberDo::getPaymentPassword, PasswordUtil.encrypt(query.getPassword()),
                MemberDo::getId, StpUtil.getLoginIdAsLong(),
                MemberDo.class
        );
    }

    @Override
    public void updateIdCard(IdCardUpdateQuery query) {
        Long memberId = StpUtil.getLoginIdAsLong();
        // 校验新身份证号
        AssertUtil.isFalse(
                memberDetailRepository.exist1Eq(
                        MemberDetailDo::getIdCard, query.getIdCard(), MemberDetailDo.class
                ),
                MemberResultEnum.ID_CARD_HAS_BOUND
        );
        // 更新身份证号
        memberDetailRepository.update1Set1Eq(
                MemberDetailDo::getIdCard, query.getIdCard(),
                MemberDetailDo::getMemberId, memberId,
                MemberDetailDo.class
        );
    }


    @Override
    public String updateAvatar(MultipartFile file) {
        Long memberId = StpUtil.getLoginIdAsLong();
        // 上传文件
        String url = ossManager.upload(file, OssConstant.MEMBER_AVATAR_PREFIX);
        // 更新头像
        memberDetailRepository.update1Set1Eq(
                MemberDetailDo::getAvatar, FileUtil.relativePath(url),
                MemberDetailDo::getMemberId, memberId,
                MemberDetailDo.class
        );
        // 返回文件绝对路径
        return url;
    }

    @Override
    public MemberVo getMember() {
        Long memberId = StpUtil.getLoginIdAsLong();
        return mapStructMember.toMemberVo(
                memberRepository.selectById(memberId),
                memberDetailRepository.selectById(memberId)
        );
    }

    @Override
    public void updateMember(MemberUpdateQuery query) {
        MemberDetailDo memberDetail = mapStructMember.toMemberDetailDo(query);
        memberDetail.setMemberId(StpUtil.getLoginIdAsLong());
        memberDetailRepository.updateById(memberDetail);
    }


    /**
     * 校验消息验证码校验通过凭证
     *
     * @param certificate 消息验证码校验通过凭证
     */
    private void assertCertificateRight(String certificate, CenterUpdateType type) {
        long memberId = StpUtil.getLoginIdAsLong();
        String dbPhone = memberRepository.<String>selectSin1Eq(
                MemberDo::getPhone,
                MemberDo::getId, memberId,
                MemberDo.class
        ).getV1();
        String phone = memberRedisManager.getMsgPassCertificate(SceneType.CENTER, certificate, dbPhone, type);
        AssertUtil.notBlank(phone, CommonResultEnum.LONG_TIME_NO_OPERATION);
        AssertUtil.equal(dbPhone, phone, CommonResultEnum.ENVIRONMENT_ABNORMAL);
    }


}
